package com.epam.cdp.jpa.services;

import java.util.Date;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import com.epam.cdp.jpa.dao.BookCopyDAO;
import com.epam.cdp.jpa.dao.BookReservationDAO;
import com.epam.cdp.jpa.dao.BookReservationRequestDAO;
import com.epam.cdp.jpa.exceptions.NoAvailableBookCopiesException;
import com.epam.cdp.jpa.exceptions.NotEnoughtPermissionsException;
import com.epam.cdp.jpa.model.Book;
import com.epam.cdp.jpa.model.BookCopy;
import com.epam.cdp.jpa.model.BookReservation;
import com.epam.cdp.jpa.model.BookReservationRequest;
import com.epam.cdp.jpa.model.BookReservationRequestStatus;
import com.epam.cdp.jpa.model.BookReservationStatus;
import com.epam.cdp.jpa.model.Role;
import com.epam.cdp.jpa.model.User;

@Stateless
public class BookReservationService {
    private static final int DEFAULT_RESERVATION_PERIOD = 4*7*24*60*60*1000;

    @EJB
    private BookReservationDAO reservationDAO;
    
    @EJB
    private BookReservationRequestDAO reservationRequestDAO;
    
    @EJB
    private BookCopyDAO bookCopyDAO;
    
    @EJB
    private ValidationService validationService;

    // in the future few aspects may be added:
    //    	- first will validate input parameters of services
    //    	- the second will wrap all public backing bean's methods and handle all exceptions
    public BookReservationRequest createReservationRequest(User readed, Book book) {
	validationService.notNull(readed, book);
	
	BookReservationRequest request = new BookReservationRequest();
	request.setBook(book);
	request.setReader(readed);
	request.setStatus(BookReservationRequestStatus.SUBMITED);
	
	reservationRequestDAO.persist(request);
	
	return request;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public synchronized BookReservation approveRequest(BookReservationRequest request, User updator) {
	validationService.notNull(request, updator);
	validateRole(updator);
	updateRequestStatus(request, BookReservationRequestStatus.APPROVED);
	
	BookReservation reservation = new BookReservation();
	reservation.setStatus(BookReservationStatus.OPENED);
	reservation.setLibrarian(updator);
	
	BookCopy appropriateBookCopy = getAppropriateBookCopy(request);
	reservation.setBookCopy(appropriateBookCopy);
	appropriateBookCopy.setAvaiable(false);
	bookCopyDAO.update(appropriateBookCopy);
	
	reservation.setStartDate(new Date());
	reservation.setEndDate(new Date(reservation.getStartDate().getTime() + DEFAULT_RESERVATION_PERIOD));
	
	reservationDAO.persist(reservation);
	
	return reservation;
    }

    public void rejectRequest(BookReservationRequest request, User updator) {
	validationService.notNull(request, updator);
	validateRole(updator);
	updateRequestStatus(request, BookReservationRequestStatus.REJECTED);
    }
    
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void closeReservation(BookReservation reservation) {
	reservation.setStatus(BookReservationStatus.CLOSED);
	reservationDAO.update(reservation);
	
	BookCopy bookCopy = reservation.getBookCopy();
	bookCopy.setAvaiable(true);
	bookCopyDAO.update(bookCopy);
    }
    
    private BookCopy getAppropriateBookCopy(BookReservationRequest request) {
	List<BookCopy> bookCopies = bookCopyDAO.getBookCopiesByStatus(request.getBook().getId(), true);
	
	if (bookCopies.isEmpty()) {
	    updateRequestStatus(request, BookReservationRequestStatus.REJECTED);
	    StringBuilder message = new StringBuilder();
	    message.append("Can't create book reservation for book ")
	    	   .append(request.getBook().getTitle())
	    	   .append(" since there is no avaiable book copies for now.");
	    throw new NoAvailableBookCopiesException(message.toString());
	}
	
	BookCopy appropriateBookCopy = bookCopies.get(0);
	return appropriateBookCopy;
    }
    
    private void updateRequestStatus(BookReservationRequest request, BookReservationRequestStatus status) {
	request.setStatus(status);
	reservationRequestDAO.update(request);
    }

    private void validateRole(User updator) {
	if (!updator.getRole().equals(Role.LIBRARIAN)) {
	    StringBuilder message = new StringBuilder();
	    message.append("User ").append(updator.getEmail())
	    	   .append(" with role ").append(updator.getRole())
	    	   .append(" can't update request status.");
	    
	    throw new NotEnoughtPermissionsException(message.toString());
	}
    }
}
